﻿using System;
using System.ComponentModel;
using System.Text;

namespace Kyoh.Lib.IO.Archive
{
    /// <summary>Zipファイルの入力に使用する <see cref="System.Text.Encoding"/> を表すクラス。</summary>
    [TypeConverter(typeof(ZipEncodingTypeConverter))]
    public class ZipEncoding : IZipEncoding
    {
        private static readonly Encoding Utf8Encoding = new UTF8Encoding(false);

        /// <summary>Zipファイルにおいて、エントリの名前およびコメントが UTF-8 でエンコーディングされていることを示すフラグ値です。</summary>
        public const short Utf8HeaderBit = 0x0800;

        /// <summary><see cref="ZipEncoding"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="encoding">原則的に使用する <see cref="System.Text.Encoding"/>。</param>
        public ZipEncoding(Encoding encoding) : this(encoding, false) { }
        
        /// <summary><see cref="ZipEncoding"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="encoding">原則的に使用する <see cref="System.Text.Encoding"/>。</param>
        /// <param name="utf8Flag">Zipファイルの utf-8フラグを考慮するかどうか。</param>
        public ZipEncoding(Encoding encoding, bool utf8Flag)
        {
            if (encoding == null)
                throw new ArgumentNullException("encoding");
            this.Encoding = encoding;
            this.Utf8Flag = utf8Flag;
        }

        #region IZipEncoding メンバ

        /// <summary>原則として使用するエンコーディングを取得します。</summary>
        public Encoding Encoding { get; private set; }

        /// <summary>ZIPファイル内のUTF8ヘッダを判定するかどうかを取得・設定します。</summary>
        public bool Utf8Flag { get; private set; }

        /// <summary>Zipエントリに使用する <see cref="System.Text.Encoding"/> を取得します。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <returns><see cref="System.Text.UTF8Encoding"/> オブジェクト、または <see cref="Encoding"/> と同一のインスタンス。</returns>
        public Encoding GetUsingEncoding(bool utf8Entry)
        {
            return this.Utf8Flag && utf8Entry
                ? Utf8Encoding
                : this.Encoding;
        }

        #endregion

        #region 文字列表現

        /// <summary><see cref="ZipEncoding"/> オブジェクトの文字列表現において、<see cref="Utf8Flag"/> プロパティが <c>true</c> であることを表す接頭語。</summary>
        public const string Utf8HeaderString = "utf-8/";
        /// <summary><see cref="Utf8HeaderString"/> の文字列長。</summary>
        public static readonly int Utf8HeaderLength;

        static ZipEncoding()
        {
            ZipEncoding.Utf8HeaderLength = ZipEncoding.Utf8HeaderString.Length;
        }

        /// <summary><see cref="ZipEncoding"/> クラスの文字列表現を取得します。</summary>
        /// <returns>このオブジェクトの文字列表現。</returns>
        public override string ToString()
        {
            return (this.Utf8Flag ? Utf8HeaderString : string.Empty)
                + (this.Encoding.BodyName);
        }

        #endregion

        #region Equals, Clone

        /// <summary><see cref="ZipEncoding"/> オブジェクトが等価かどうかを取得します。</summary>
        /// <param name="other">比較対象の <see cref="ZipEncoding"/>。</param>
        /// <returns>対象がこのオブジェクトと等価であれば <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public bool Equals(IZipEncoding other)
        {
            return other == null
                 ? false
                 : (this.Encoding == other.Encoding &&
                    this.Utf8Flag == other.Utf8Flag);
        }

        /// <summary>オブジェクトが等価かどうかを取得します。</summary>
        /// <param name="obj">比較対象のオブジェクト。</param>
        /// <returns>対象がこのオブジェクトと等価であれば <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as ZipEncoding);
        }

        /// <summary>このオブジェクトのハッシュ値を取得します。</summary>
        /// <returns>このオブジェクトのハッシュ値。</returns>
        public override int GetHashCode()
        {
            return this.Encoding.CodePage * (this.Utf8Flag ? -1 : 1);
        }

        #endregion

        #region IZipConverter メンバ

        #region GetByteCount

        /// <summary>指定した文字配列に格納されているすべての文字をエンコードすることによって生成されるバイト数を計算します。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="chars">エンコード対象の文字を格納している文字配列。</param>
        /// <returns>指定した文字配列に格納されているすべての文字をエンコードすることによって生成されるバイト数。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="chars"/> が <c>null</c> です。</exception>
        /// <exception cref="EncoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="EncoderFallback"/> が <see cref="EncoderExceptionFallback"/> に設定されています。 </exception>
        public int GetByteCount(bool utf8Entry, char[] chars)
        {
            return this.GetUsingEncoding(utf8Entry).GetByteCount(chars);
        }

        /// <summary>指定した文字配列に格納されている文字のセットをエンコードすることによって生成されるバイト数を計算します。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="chars">エンコード対象の文字のセットを格納している文字配列。</param>
        /// <param name="index">エンコードする最初の文字のインデックス。</param>
        /// <param name="count">エンコードする文字数。</param>
        /// <returns>指定した文字をエンコードすることによって生成されるバイト数。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="chars"/> が <c>null</c> です。</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> または <paremref name="count"/> が <c>0</c> 未満です。 または <paramref name="index"/> および <paramref name="count"/> が <paramref name="chars"/> 内の有効な範囲を示していません。</exception>
        /// <exception cref="EncoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="EncoderFallback"/> が <see cref="EncoderExceptionFallback"/> に設定されています。 </exception>
        public int GetByteCount(bool utf8Entry, char[] chars, int index, int count)
        {
            return this.GetUsingEncoding(utf8Entry).GetByteCount(chars, index, count);
        }

        /// <summary>指定した<see cref="String"/> に含まれる文字をエンコードすることによって生成されるバイト数を計算します。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="s">エンコード対象の文字のセットを格納している<see cref="String"/>。</param>
        /// <returns>指定した文字をエンコードすることによって生成されるバイト数。</returns>
        /// <exception cref="ArgumentNullException">s が <c>null</c> です。</exception>
        /// <exception cref="EncoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="EncoderFallback"/> が <see cref="EncoderExceptionFallback"/> に設定されています。 </exception>
        public int GetByteCount(bool utf8Entry, string s)
        {
            return this.GetUsingEncoding(utf8Entry).GetByteCount(s);
        }

        #endregion

        #region GetCharCount

        /// <summary>指定したバイト配列に格納されているすべてのバイトをデコードすることによって生成される文字数を計算します。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="bytes">デコード対象のバイト シーケンスが格納されたバイト配列。</param>
        /// <returns>指定したバイト シーケンスをデコードすることによって生成される文字数。</returns>
        /// <exception cref="ArgumentNullException"><paremref name="bytes"/> が <c>null</c> です。</exception>
        /// <exception cref="DecoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="DecoderFallback"/> が <see cref="DecoderExceptionFallback"/> に設定れています。 </exception>
        public int GetCharCount(bool utf8Entry, byte[] bytes)
        {
            return this.GetUsingEncoding(utf8Entry).GetCharCount(bytes);
        }

        /// <summary>指定したバイト配列に格納されているバイト シーケンスをデコードすることによって生成される文字数を計算します。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="bytes">デコード対象のバイト シーケンスが格納されたバイト配列。</param>
        /// <param name="index">デコードする最初のバイトのインデックス。</param>
        /// <param name="count">デコードするバイト数。</param>
        /// <returns>指定したバイト シーケンスをデコードすることによって生成される文字数。</returns>
        /// <exception cref="ArgumentNullException"><paremref name="bytes"/> が <c>null</c> です。</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> または <paremref name="count"/> が <c>0</c> 未満です。 または <paramref name="index"/> および <paramref name="count"/> が <paramref name="bytes"/> 内の有効な範囲を示していません。</exception>
        /// <exception cref="DecoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="DecoderFallback"/> が <see cref="DecoderExceptionFallback"/> に設定れています。 </exception>
        public int GetCharCount(bool utf8Entry, byte[] bytes, int index, int count)
        {
            return this.GetUsingEncoding(utf8Entry).GetCharCount(bytes, index, count);
        }

        #endregion

        #region GetBytes

        /// <summary>指定した文字配列に格納されているすべての文字をバイト シーケンスにエンコードします。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="chars">エンコード対象の文字を格納している文字配列。</param>
        /// <returns>指定した文字のセットをエンコードした結果を格納しているバイト配列。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="chars"/> が <c>null</c> です。</exception>
        /// <exception cref="EncoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="EncoderFallback"/> が <see cref="EncoderExceptionFallback"/> に設定されています。 </exception>
        public byte[] GetBytes(bool utf8Entry, char[] chars)
        {
            return this.GetUsingEncoding(utf8Entry).GetBytes(chars);
        }

        /// <summary>指定した文字配列に格納されている文字のセットをバイト シーケンスにエンコードします。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="chars">エンコード対象の文字のセットを格納している文字配列。</param>
        /// <param name="index">エンコードする最初の文字のインデックス。</param>
        /// <param name="count">エンコードする文字数。</param>
        /// <returns>指定した文字のセットをエンコードした結果を格納しているバイト配列。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="chars"/> が <c>null</c> です。</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> または <paremref name="count"/> が <c>0</c> 未満です。 または <paramref name="index"/> および <paramref name="count"/> が <paramref name="chars"/> 内の有効な範囲を示していません。</exception>
        /// <exception cref="EncoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="EncoderFallback"/> が <see cref="EncoderExceptionFallback"/> に設定されています。 </exception>
        public byte[] GetBytes(bool utf8Entry, char[] chars, int index, int count)
        {
            return this.GetUsingEncoding(utf8Entry).GetBytes(chars, index, count);
        }

        /// <summary>指定した<see cref="String"/> に含まれるすべての文字をバイト シーケンスにエンコードします。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="s">エンコード対象の文字のセットを格納している<see cref="String"/>。</param>
        /// <returns>指定した文字のセットをエンコードした結果を格納しているバイト配列。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="s"/> が <c>null</c> です。</exception>
        /// <exception cref="EncoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="EncoderFallback"/> が <see cref="EncoderExceptionFallback"/> に設定されています。 </exception>
        public byte[] GetBytes(bool utf8Entry, string s)
        {
            return this.GetUsingEncoding(utf8Entry).GetBytes(s);
        }

        #endregion

        #region GetChars

        /// <summary>指定したバイト配列に格納されているすべてのバイトを文字のセットにデコードします。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="bytes">デコード対象のバイト シーケンスが格納されたバイト配列。</param>
        /// <returns>指定したバイト シーケンスのデコード結果が格納された文字配列。</returns>
        /// <exception cref="ArgumentNullException"><paremref name="bytes"/> が <c>null</c> です。</exception>
        /// <exception cref="DecoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="DecoderFallback"/> が <see cref="DecoderExceptionFallback"/> に設定れています。 </exception>
        public char[] GetChars(bool utf8Entry, byte[] bytes)
        {
            return this.GetUsingEncoding(utf8Entry).GetChars(bytes);
        }

        /// <summary>指定したバイト配列に格納されているバイト シーケンスを文字のセットにデコードします。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="bytes">デコード対象のバイト シーケンスが格納されたバイト配列。</param>
        /// <param name="index">デコードする最初のバイトのインデックス。</param>
        /// <param name="count">デコードするバイト数。</param>
        /// <returns>指定したバイト シーケンスのデコード結果が格納された文字配列。</returns>
        /// <exception cref="ArgumentNullException"><paremref name="bytes"/> が <c>null</c> です。</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> または <paremref name="count"/> が <c>0</c> 未満です。 または <paramref name="index"/> および <paramref name="count"/> が <paramref name="bytes"/> 内の有効な範囲を示していません。</exception>
        /// <exception cref="DecoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="DecoderFallback"/> が <see cref="DecoderExceptionFallback"/> に設定れています。 </exception>
        public char[] GetChars(bool utf8Entry, byte[] bytes, int index, int count)
        {
            return this.GetUsingEncoding(utf8Entry).GetChars(bytes, index, count);
        }

        #endregion

        #region GetString

        /// <summary>指定したバイト配列に格納されているすべてのバイトを文字列にデコードします。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="bytes">デコード対象のバイト シーケンスが格納されたバイト配列。</param>
        /// <returns>指定したバイト シーケンスのデコード結果が格納されている<see cref="String"/>。</returns>
        /// <exception cref="ArgumentNullException"><paremref name="bytes"/> が <c>null</c> です。</exception>
        /// <exception cref="DecoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="DecoderFallback"/> が <see cref="DecoderExceptionFallback"/> に設定れています。 </exception>
        public string GetString(bool utf8Entry, byte[] bytes)
        {
            return this.GetUsingEncoding(utf8Entry).GetString(bytes);
        }

        /// <summary>指定したバイト配列に格納されているバイト シーケンスを文字列にデコードします。</summary>
        /// <param name="utf8Entry">エントリのUTF-8フラグがオンであるかどうかを示す <see cref="Boolean"/> 値。</param>
        /// <param name="bytes">デコード対象のバイト シーケンスが格納されたバイト配列。</param>
        /// <param name="index">デコードする最初のバイトのインデックス。</param>
        /// <param name="count">デコードするバイト数。</param>
        /// <returns>指定したバイト シーケンスのデコード結果が格納される<see cref="String"/>。</returns>
        /// <exception cref="ArgumentNullException"><paremref name="bytes"/> が <c>null</c> です。</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> または <paremref name="count"/> が <c>0</c> 未満です。 または <paramref name="index"/> および <paramref name="count"/> が <paramref name="bytes"/> 内の有効な範囲を示していません。</exception>
        /// <exception cref="DecoderFallbackException">フォールバックが発生しました (詳細については、「エンコーディングの理解」を参照してください)。 および <see cref="DecoderFallback"/> が <see cref="DecoderExceptionFallback"/> に設定れています。 </exception>
        public string GetString(bool utf8Entry, byte[] bytes, int index, int count)
        {
            return this.GetUsingEncoding(utf8Entry).GetString(bytes, index, count);
        }

        #endregion

        #endregion
    }
}